﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;

using SeriesManager.Enum;
using SeriesManager.Model;
using SeriesManager.Extension;
using System.Net.Http;
using System.Xml.Linq;
using Windows.Storage;
using System.IO;
using System.IO.Compression;
using SeriesManager.Model.Banner;
using System.Globalization;
using SeriesManager.Configuration;

namespace SeriesManager.API
{
    public class TVDB : IApi
    {
        private const string API_KEY = "B8489AFD55EF0375";
        private const string API_BANNER = "http://thetvdb.com/banners/";
        private const string API_SERIES = "http://thetvdb.com/api/";
        private readonly IConfigurationData configuration = new RoamingConfigurationData();

        private async Task<XDocument> FolderToXML(StorageFolder folder, Languages language)
        {
            if (folder == null)
                return null;

            XDocument doc = null;
            string[] names = { language.ToShort(), "Actors", "Banners" };

            foreach (string name in names)
            {
                try
                {
                    var file = await folder.GetFileAsync(name.ToLower() + ".xml");
                    var content = await FileIO.ReadTextAsync(file);
                    if (doc == null)
                    {
                        doc = XDocument.Parse(content);
                        continue;
                    }

                    var tmp = XDocument.Parse(content);
                    doc.Element("Data").Add(tmp.Element(name));
                }
                catch { return null; }
            }

            return doc;
        }
    
        public async Task Update(SeriesModel series)
        {
            string shortLanguage = series.Language.ToShort();

            var seriesFolder = (StorageFolder)await ApplicationData.Current.LocalFolder.TryGetItemAsync(series.ID.ToString());
            if (seriesFolder == null || (await seriesFolder.TryGetItemAsync(shortLanguage + ".xml")) == null)
            {
                string url = API_SERIES + API_KEY + "/series/" + series.ID + "/all/" + shortLanguage + ".zip";
                Stream stream = null;

                try
                {
                    HttpClient httpClient = new HttpClient();
                    HttpResponseMessage response = await httpClient.GetAsync(url);
                    response.EnsureSuccessStatusCode();
                    stream = await response.Content.ReadAsStreamAsync();
                }
                catch { return; }

                if (seriesFolder == null)
                {
                    seriesFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(series.ID.ToString());
                }
                using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read))
                {
                    foreach (var entry in archive.Entries)
                    {
                        StorageFile file = null;

                        if (await seriesFolder.TryGetItemAsync(entry.Name) != null) continue;

                        file = await seriesFolder.CreateFileAsync(entry.Name, CreationCollisionOption.FailIfExists);
                        using (var reader = new StreamReader(entry.Open()))
                        {
                            string xmlText = reader.ReadToEnd();
                            await FileIO.WriteTextAsync(file, xmlText);
                        }
                    }
                }
            }
            
            var newContent = await this.FolderToXML(seriesFolder, series.Language);
            XMLToSeries(newContent, series);
        }

        public async Task<IEnumerable<SeriesModel>> Search(string searchQuery, Languages language)
        {
            List<SeriesModel> searchResult = new List<SeriesModel>();

            if (!string.IsNullOrWhiteSpace(searchQuery))
            {
                string shortLanguage = language.ToShort();
                string content = API_SERIES + "GetSeries.php?seriesname=" + searchQuery + "&language=" + shortLanguage;

                try
                {
                    HttpClient httpClient = new HttpClient();
                    HttpResponseMessage response = await httpClient.GetAsync(content);
                    response.EnsureSuccessStatusCode();
                    content = await response.Content.ReadAsStringAsync();
                }
                catch { return searchResult; }

                var doc = XDocument.Parse(content);
                var data = doc.Element("Data");
                var seriesData = data.Elements("Series");

                foreach (XElement series in seriesData)
                {
                    // Check if existing
                    if (series.Element("id") != null)
                    {
                        int id;
                        if (!int.TryParse(series.Element("id").Value, out id))
                            continue;

                        XElement langElement = series.Element("Language") == null ? series.Element("language") : series.Element("Language");
                        if (langElement == null)
                            continue;

                        if (langElement.Value == shortLanguage)
                        {
                            XDocument tmpDoc = new XDocument(new XElement("Data", series));
                            SeriesModel s = XMLToSeries(tmpDoc);
                            if (s != null)
                            {
                                await this.Update(s);
                                searchResult.Add(s);
                            }
                        }
                    }
                }
            }

            return searchResult;
        }

        public async Task<IEnumerable<SeriesModel>> Load(Languages language)
        {
            List<SeriesModel> loadResult = new List<SeriesModel>();

            var folders = await ApplicationData.Current.LocalFolder.GetFoldersAsync();
            foreach (var folder in folders)
            {
                XDocument seriesContent = await this.FolderToXML(folder, language);
                if (seriesContent == null)
                    continue;

                SeriesModel series = XMLToSeries(seriesContent);
                loadResult.Add(series);
            }

            var subscriptions = ApplicationData.Current.RoamingSettings.Containers;
            foreach (var sub in subscriptions)
            {
                if (!loadResult.Any(S => S.ID.ToString() == sub.Key))
                {
                    var series = new SeriesModel(Int32.Parse(sub.Key));
                    series.Language = language;
                    await this.Update(series);
                    loadResult.Add(series);
                }
            }

            return loadResult;
        }

        public async Task Clear()
        {
            foreach (var folder in await ApplicationData.Current.LocalFolder.GetFoldersAsync())
            {
                int seriesID;
                if (!Int32.TryParse(folder.Name, out seriesID))
                    continue;

                if (!ApplicationData.Current.RoamingSettings.Containers.ContainsKey(seriesID.ToString()))
                {
                    var item = await ApplicationData.Current.LocalFolder.TryGetItemAsync(seriesID.ToString());
                    if (item != null)
                    {
                        await item.DeleteAsync(StorageDeleteOption.PermanentDelete);
                    }
                }
            }
        }

        private SeriesModel XMLToSeries(XDocument doc, SeriesModel seriesModel = null)
        {
            if (doc == null || doc.Element("Data") == null)
                throw new ArgumentNullException();

            var series = doc.Element("Data").Element("Series");

            if (series == null || series.Element("id") == null)
                throw new NullReferenceException();

            int id;
            if (!int.TryParse(series.Element("id").Value, out id))
                throw new Exception("Series needs to contain an id.");

            if (seriesModel == null)
            {
                seriesModel = new SeriesModel(id);
                configuration.Load(new[] { seriesModel });
                configuration.Activate(new[] { seriesModel });
            }

            // Update Actors
            var actorCol = doc.Element("Data").Element("Actors");
            if (actorCol != null)
            {
                UpdateActors(actorCol, seriesModel);
            }

            // Update Banners
            var bannerCol = doc.Element("Data").Element("Banners");
            if (bannerCol != null)
            {
                UpdateBanners(bannerCol, seriesModel);
            }

            // Update Series Info
            UpdateSeriesInfo(series, seriesModel);

            // Update Episodes
            var epiCol = doc.Element("Data").Elements("Episode");
            if (epiCol != null)
                UpdateEpisodeInfo(epiCol, seriesModel);

            // Update FanArt
            UpdateFanArt(series.Element("fanart"), seriesModel);

            return seriesModel;
        }

        private void UpdateFanArt(XElement elem, SeriesModel seriesModel)
        {
            if (seriesModel.FanArt != null)
            {
                var fanArt = (FanArtBannerModel)seriesModel.FanArtBanners.FirstOrDefault(F => F.ID == seriesModel.FanArt.ID);
                if (fanArt != null)
                {
                    seriesModel.FanArt = fanArt;
                    return;
                }
            }

            var first = (FanArtBannerModel)seriesModel.FanArtBanners.FirstOrDefault();
            if (first != null)
            {
                seriesModel.FanArt = first;
                return;
            }

            if (elem != null)
            {
                seriesModel.FanArt = new FanArtBannerModel(seriesModel.ID, elem.Value);
            }
        }

        private void UpdateActors(XElement actorsXML, SeriesModel seriesModel)
        {
            if (actorsXML != null)
            {
                foreach (XElement actorXML in actorsXML.Elements("Actor"))
                {
                    int actorID;
                    var actorIDXML = actorXML.Element("id");
                    if (actorIDXML == null || !Int32.TryParse(actorIDXML.Value, out actorID)) 
                    {
                        continue;
                    }
                    
                    ActorModel actor = seriesModel.Actors.FirstOrDefault(A => A.ID == actorID);
                    if (actor == null) 
                    {
                        actor = new ActorModel(actorID);
                        seriesModel.Actors.Add(actor);
                    }

                    foreach (XElement elem in actorXML.Elements())
                    {
                        if (elem.Name == "Image")
                        {
                            actor.Image = new BannerBaseModel(seriesModel.ID, elem.Value);
                        }
                        else if (elem.Name == "Name")
                        {
                            actor.Name = elem.Value;
                        }
                        else if (elem.Name == "Role")
                        {
                            actor.Role = elem.Value;
                        }
                        else if (elem.Name == "SortOrder")
                        {
                            int sortOrder;
                            if (Int32.TryParse(elem.Value, out sortOrder))
                            {
                                actor.SortOrder = sortOrder;
                            }
                        }
                    }
                }
            }
        }

        private void UpdateBanners(XElement bannersXML, SeriesModel seriesModel)
        {
            if (bannersXML == null)
            {
                throw new ArgumentNullException();
            }

            foreach (XElement bannerXML in bannersXML.Elements("Banner"))
            {
                int bannerID;
                var bannerXMLID = bannerXML.Element("id");
                if (bannerXMLID == null || !Int32.TryParse(bannerXMLID.Value, out bannerID))
                {
                    continue;
                }

                BannerBaseModel banner = null;

                string bannerType = bannerXML.Element("BannerType").Value;
                if (bannerType == "fanart") 
                {
                    banner = seriesModel.FanArtBanners.FirstOrDefault(B => B.ID == bannerID);
                    if (banner == null) 
                    {
                        banner = new FanArtBannerModel(seriesModel.ID);
                        seriesModel.FanArtBanners.Add((FanArtBannerModel)banner);
                    }
                        
                    (banner as FanArtBannerModel).ThumbnailRemotePath = bannerXML.Element("ThumbnailPath").Value;
                    var size = bannerXML.Element("BannerType2").Value;
                    int index = size.IndexOf('x');
                    if (index > 0)
                    {
                        (banner as FanArtBannerModel).Width = int.Parse(size.Substring(0, index));
                        (banner as FanArtBannerModel).Height = int.Parse(size.Substring(index + 1));
                    }
                }
                else if (bannerType == "poster")
                {
                    banner = seriesModel.PosterBanners.FirstOrDefault(B => B.ID == bannerID);
                    if (banner == null) 
                    {
                        banner = new PosterBannerModel(seriesModel.ID);
                        seriesModel.PosterBanners.Add((PosterBannerModel)banner);
                    }

                    var size = bannerXML.Element("BannerType2").Value;
                    int index = size.IndexOf('x');
                    if (index > 0)
                    {
                        (banner as PosterBannerModel).Width = int.Parse(size.Substring(0, index));
                        (banner as PosterBannerModel).Height = int.Parse(size.Substring(index + 1));
                    }
                }
                else if (bannerType == "season")
                {
                    banner = seriesModel.SeasonBanners.FirstOrDefault(B => B.ID == bannerID);
                    if (banner == null) 
                    {
                        banner = new SeasonBannerModel(seriesModel.ID);
                        seriesModel.SeasonBanners.Add((SeasonBannerModel)banner);
                    }

                    (banner as SeasonBannerModel).Number = Int32.Parse(bannerXML.Element("Season").Value);
                    SeriesManager.Model.Banner.SeasonBannerModel.SeasonSize size;
                    if (System.Enum.TryParse(bannerXML.Element("BannerType2").Value, out size))
                    {
                        (banner as SeasonBannerModel).Size = size;
                    }
                }
                else if (bannerType == "series")
                {
                    banner = seriesModel.WideBanners.FirstOrDefault(B => B.ID == bannerID);
                    if (banner == null) 
                    {
                        banner = new WideBannerModel(seriesModel.ID);
                        seriesModel.WideBanners.Add((WideBannerModel)banner);
                    }

                    SeriesManager.Model.Banner.WideBannerModel.SeriesType type;
                    if (System.Enum.TryParse(bannerXML.Element("BannerType2").Value, out type))
                    {
                        (banner as WideBannerModel).Type = type;
                    }
                }

                if (banner != null)
                {
                    banner.ID = bannerID;
                    banner.RemotePath = bannerXML.Element("BannerPath").Value;
                    banner.Language = bannerXML.Element("Language").Value.ToLanguage();

                    double rating;
                    if (Double.TryParse(bannerXML.Element("Rating").Value, out rating))
                    {
                        banner.Rating = rating;
                    }

                    int ratingCount;
                    if (Int32.TryParse(bannerXML.Element("RatingCount").Value, out ratingCount))
                    {
                        banner.RatingCount = ratingCount;
                    }
                }
            }
        }

        private void UpdateSeriesInfo(XElement series, SeriesModel seriesModel)
        {
            if (series == null)
            {
                throw new ArgumentNullException();
            }

            foreach (XElement elem in series.Elements())
            {
                if (elem.Name == "SeriesName")
                {
                    seriesModel.Name = elem.Value;
                }
                else if (elem.Name == "Rating")
                {
                    double rating;
                    if (double.TryParse(elem.Value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
                        seriesModel.Rating = rating;
                }
                else if (elem.Name == "Airs_Time")
                {
                    string airTime = elem.Value.Replace(" ", "");
                    DateTime tmp;
                    if (DateTime.TryParse(airTime, out tmp))
                    {
                        seriesModel.AirTime = tmp.TimeOfDay;
                    }
                }
                else if (elem.Name == "Airs_DayOfWeek")
                {
                    DayOfWeek airDay;
                    if (System.Enum.TryParse<DayOfWeek>(elem.Value, out airDay))
                        seriesModel.AirDay = airDay;
                }
                else if (elem.Name == "FirstAired")
                {
                    DateTime firstAired;
                    if (DateTime.TryParse(elem.Value, out firstAired))
                        seriesModel.FirstAired = firstAired;
                }
                else if (elem.Name == "Runtime")
                {
                    byte runtime;
                    if (byte.TryParse(elem.Value, out runtime))
                        seriesModel.Runtime = runtime;
                }
                else if (elem.Name == "Genre")
                {
                    seriesModel.Genre = elem.Value.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                }
                else if (elem.Name == "Network")
                {
                    seriesModel.Network = elem.Value;
                }
                else if (elem.Name == "Overview")
                {
                    seriesModel.Overview = elem.Value.Trim();
                }
                else if (elem.Name == "Status")
                {
                    seriesModel.Active = elem.Value == "Continuing";
                }
                else if (elem.Name.ToString().ToLower() == "language")
                {
                    seriesModel.Language = elem.Value.ToLanguage();
                }
                else if (elem.Name == "IMDB_ID")
                {
                    seriesModel.ImdbID = elem.Value;
                }
                else if (elem.Name == "Rating")
                {
                    double rating;
                    if (Double.TryParse(elem.Value, out rating))
                        seriesModel.Rating = rating;
                }
                else if (elem.Name == "RatingCount") 
                {
                    int ratingCount;
                    if (Int32.TryParse(elem.Value, out ratingCount))
                        seriesModel.RatingCount = ratingCount;
                }
            }
        }

        private void UpdateEpisodeInfo(IEnumerable<XElement> episodesXML, SeriesModel seriesModel)
        {
            if (episodesXML == null)
            {
                throw new ArgumentException();
            }
                        
            foreach (XElement episodeXML in episodesXML)
            {
                var seasonIDXML = episodeXML.Element("seasonid");
                int seasonID;
                if (seasonIDXML == null || seasonIDXML.Value == null || !int.TryParse(seasonIDXML.Value, out seasonID))
                    continue;

                var epiSeason = episodeXML.Element("SeasonNumber");
                int seasonNumber;
                if (epiSeason == null || epiSeason.Value == null || !int.TryParse(epiSeason.Value, out seasonNumber) || seasonNumber < 1)
                    continue;

                var idElem = episodeXML.Element("id");
                int id;
                if (idElem == null || idElem.Value == null || string.IsNullOrWhiteSpace(idElem.Value) || !Int32.TryParse(idElem.Value, out id))
                    continue;

                var numberElem = episodeXML.Element("EpisodeNumber");
                int number;
                if (numberElem == null || numberElem.Value == null || string.IsNullOrWhiteSpace(numberElem.Value) || !Int32.TryParse(numberElem.Value, out number) || number <= 0)
                    continue;

                var firstAiredElem = episodeXML.Element("FirstAired");
                DateTime firstAired = new DateTime();
                if (firstAiredElem != null && firstAiredElem.Value != null)
                {
                    DateTime.TryParse(firstAiredElem.Value, out firstAired);
                }

                var sBanner = seriesModel.SeasonBanners.FirstOrDefault(B => B.Number == seasonNumber);
                var season = seriesModel.Seasons.FirstOrDefault(S => S.ID == seasonID);
                if (season == null)
                {
                    season = new SeasonModel(seasonID, seriesModel);
                    season.Number = seasonNumber;
                    seriesModel.Seasons.Add(season);
                }
                season.Banner = sBanner;

                var episode = season.Episodes.FirstOrDefault(E => E.ID == id);
                bool needAdding = false;
                if (episode == null)
                {
                    episode = new EpisodeModel(id, season, firstAired);
                    episode.Number = number;
                    needAdding = true;
                }

                foreach (XElement elem in episodeXML.Elements())
                {
                    if (elem.Name == "EpisodeName")
                    {
                        episode.Name = elem.Value;
                    }
                    else if (elem.Name == "Director")
                    {
                        episode.Director = elem.Value.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                    }
                    else if (elem.Name == "GuestStars")
                    {
                        episode.GuestStars = elem.Value.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                    }
                    else if (elem.Name == "Overview")
                    {
                        episode.Overview = elem.Value.Trim();
                    }
                    else if (elem.Name == "Rating")
                    {
                        double rating;
                        if (double.TryParse(elem.Value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
                            episode.Rating = rating;
                    }
                    else if (elem.Name == "Writer")
                    {
                        episode.Writer = elem.Value.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                    }
                    else if (elem.Name == "filename")
                    {
                        episode.Image = new BannerBaseModel(seriesModel.ID, elem.Value);
                    }
                }

                if (needAdding)
                {
                    season.Episodes.Add(episode);
                }
            }
        }
    }
}
